<template>
	<!-- 用view标签无法正确获取到scrollTop，所以这里改为div标签 -->
	<div class="chat" ref="ChatBox">
		<!-- <view class="list" :style="{'height':containerHeight + 'px'}">
			<view class="item" v-for="(item,index) in viewList" :key="index" :id="item.idx"
				:style="{'top':item.top + 20 + 'px'}">
				<view :class="['msg-box',item.role==='user'?'msg-user':'msg-sys']">
					<view :class="['icon flex-box',item.role==='user'?'icon-user':'icon-sys']">
						{{item.role==='user'?'User':'Tumi'}}
					</view>
					<view class="msg border-box" :style="{'background':item.role==='user'?'#1F429C':'var(--fu-color)'}">
						{{item.content}}
					</view>
				</view>
			</view>
		</view> -->
		<view class="list" >
			<chatItemVue :dataSource="props.list" :estimatedHeight="120">
				<template #item="{item}">
					<view class="item">
						<view :class="['msg-box',item.role==='user'?'msg-user':'msg-sys']">
							<view :class="['icon flex-box',item.role==='user'?'icon-user':'icon-sys']">
								{{item.role==='user'?'User':'Tumi'}}
							</view>
							<view class="msg border-box"
								:style="{'background':item.role==='user'?'#1F429C':'var(--fu-color)'}">
								{{item.content}}
							</view>
						</view>
					</view>
				</template>
			</chatItemVue>
		</view>
	</div>
</template>

<script setup lang="ts">
	import chatItemVue from './chatItem.vue';
	import { computed, onMounted, ref } from 'vue';
	const props = defineProps({
		list: {
			type: Array,
			default: () => []
		}
	})
	const itemHeight = ref(100) //预测每一项的高度
	const viewList = ref([]) //视图中可见的列表
	const ChatBox = ref(null)

	const measuredData = ref({
		measuredDataMap: [], // 存储已经计算过的元素
		LastMeasuredItemIndex: -1, // 最后一次被计算的元素的索引
	})


	/**
	 * 获取指定索引的元素的高度和偏移量
	 */
	const getItemMetaData = (index : number) => {
		const { LastMeasuredItemIndex, measuredDataMap } = measuredData.value
		// 如果index大于当前记录的最大值，挨个计算到index去，用top+height一个一个计算
		if (index > LastMeasuredItemIndex) {
			// 如果LastMeasuredItemIndex大于等于0的话topOffset就直接用measuredDataMap[LastMeasuredItemIndex]的值去累加后面的就行了
			let topOffset = LastMeasuredItemIndex >= 0 ? measuredDataMap[LastMeasuredItemIndex].topOffset + measuredDataMap[LastMeasuredItemIndex].height : 0
			for (let i = LastMeasuredItemIndex + 1; i <= index; i++) {
				// 这里用的都是默认高度，因为后面渲染时会更新缓存数据
				measuredDataMap[i] = { height: itemHeight.value, topOffset };
				topOffset += itemHeight.value;
			}
		}
		// 把当前索引作为最后一次被计算的索引，方便下次计算的时候省略掉重复计算
		measuredData.value.LastMeasuredItemIndex = index
		return measuredDataMap[index]
	}

	/**
	 * 获取当前可视区域内的起始坐标
	 */
	const handleGetStartIndex = (scrollTop : number) => {
		// 采用二分查找
		let star = 0
		let end = props.list.length - 1
		while (star <= end) {
			const mid = Math.floor((star + end) / 2);
			const currentOffset = getItemMetaData(mid).topOffset;
			if (currentOffset === scrollTop) {
				return mid;
			} else if (currentOffset < scrollTop) {
				star = mid + 1;
			} else {
				end = mid - 1;
			}
		}
		return star
	}
	/**
	 * 获取当前可视区域内的结束坐标
	 */
	const handleGetEndIndex = (startIndex : number) => {
		const listHeight = ChatBox.value.offsetHeight // 可视区域的高度
		const startItem = getItemMetaData(startIndex)
		const maxOffset = startItem.topOffSet + listHeight
		let offset = startItem.topOffset + startItem.height;
		let endIndex = startIndex;

		while (offset <= maxOffset && endIndex < props.list.length - 1) {
			endIndex++;
			const currentItem = getItemMetaData(endIndex);
			offset += currentItem.height;
		}
		return endIndex;
	}
	// 获取当前可视的范围
	const getChildShowRange = (scrollOffset : number) => {
		const itemSumCount = props.list.length
		const bufferNum = 6 // 缓冲区的数量
		const startIndex = handleGetStartIndex(scrollOffset);
		const endIndex = handleGetEndIndex(startIndex);
		const bufferStartIndex = Math.max(0, startIndex - bufferNum)
		const bufferEndIndex = Math.min(itemSumCount - 1, endIndex + bufferNum)
		const items = []
		for (let i = bufferStartIndex; i <= bufferEndIndex; i++) {
			const item = getItemMetaData(i);
			items.push({
				top: item.topOffset,
				...props.list[i] as Object,
				idx: i
			});
		}
		viewList.value = items
		handleObserveDomToUpdate()
	}
	// 观察实际高度 更新数据
	const handleObserveDomToUpdate = () => {
		const dom = document.getElementsByClassName('item')
		for (let i = 0; i < dom.length; i++) {
			const height = (dom[i] as HTMLDivElement).offsetHeight;
			const idx = (dom[i] as HTMLDivElement).id;
			const { measuredDataMap, LastMeasuredItemIndex } = measuredData.value;
			measuredDataMap[idx].height = height;
			let offset = 0;
			// 重新计算偏移值
			for (let i = 0; i <= LastMeasuredItemIndex; i++) {
				measuredDataMap[i].topOffset = offset;
				offset += measuredDataMap[i].height;
			}
			dom[i].style.height = height + 'px';
		}
	}


	const containerHeight = computed(() => {
		return props.list.length * itemHeight.value
	}) // 容器总高度

	/**
	 * 监听滚动条滚动
	 */
	const handleScroll = (e : any) => {
		getChildShowRange(e.target.scrollTop)
	}

	/**
	 * 计算可视区域的列表  固定高度
	 */
	const handleComputedViewList = (scrollTop : number) => {
		const startIndex = scrollTop === 0 ? 0 : Math.floor(scrollTop / itemHeight.value)
		const height = ChatBox.value.offsetHeight
		const pushNum = Math.ceil(height / itemHeight.value)
		const endIndex = (startIndex + pushNum) > props.list.length - 1 ? props.list.length - 1 : startIndex + pushNum
		const list = []
		for (let i = startIndex; i <= endIndex; i++) {
			list.push({
				...props.list[i] as Object,
				top: i * itemHeight.value,
				idx: i,
			})
		}
		viewList.value = list
	}

	onMounted(() => {
		getChildShowRange(0)
	})
</script>

<style lang="scss" scoped>
	.chat {
		height: calc(100vh - 278rpx);
		overflow-y: auto;

		.list {
			padding: 20rpx 0;
			position: relative;

			.item {
				// margin-bottom: 40rpx;
				position: absolute;
				padding: 0 20rpx;
				width: 100%;
				box-sizing: border-box;

				.msg-box {
					display: flex;
				}

				.icon {
					width: 80rpx;
					height: 80rpx;
					border-radius: 50%;
					font-size: 24rpx;
					color: #fff;
				}

				.icon-user {
					background: #1F429C;
					margin-left: 10rpx;
				}

				.icon-sys {
					background: #6F7899;
					margin-right: 10rpx;
				}

				.msg {
					padding: 20rpx;
					border-radius: 10rpx;
					max-width: calc(100% - 190rpx);
					font-size: 24rpx;
					color: #fff;
				}
			}

			.msg-user {
				flex-direction: row-reverse;
			}

			.msg-sys {}
		}
	}
</style>